﻿
namespace Huirui.Cavan.Core.Persistent
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Globalization;
    using System.Linq;
    using Huirui.Cavan.Core.Extension;

    /// <summary>
    ///     Base class for persistent object. Provides common interface to allow comparing two instances 
    ///     (Equal, GetHashCode, GetDomainObjectSignature) and UI handling ToDisplay(), IDToDisplay
    /// </summary>
    [Serializable]
    public abstract class PersistentObjectWithTypedId<TID>
        : IPersistentObject
          , IPersistentObjectWithTypedId<TID>
          , IComparable<PersistentObjectWithTypedId<TID>>
          , IComparable
    {
        #region IComparable Members
        /// <summary>
        ///     If 'obj' is PersistentObjectWithTypedId
        ///     Provides comparison based on the GetDomainObjectSignature() method of both objects 
        ///     else thorws exception
        /// </summary>
        /// <param name = "obj">PersistentObjectWithTypedId to be compared</param>
        /// <returns></returns>
        public virtual int CompareTo(object obj)
        {
            var other = obj as PersistentObjectWithTypedId<TID>;
            if (other.Is())
            {
                return CompareTo(other);
            }
            return 1;
        }
        #endregion

        #region IComparable<PersistentObjectWithTypedId<TID>> Members
        /// <summary>
        ///     Provides comparison based on the GetDomainObjectSignature() method of both objects
        /// </summary>
        /// <param name = "other"></param>
        /// <returns></returns>
        public virtual int CompareTo(PersistentObjectWithTypedId<TID> other)
        {
            if (this.IsNull())
            {
                return -1;
            }
            return other.IsNull()
                       ? 1
                       : GetDomainObjectSignature().CompareTo(other.GetDomainObjectSignature());
        }
        #endregion

        #region operator
        /// <summary>
        ///     Provides the CompareTo result for two PersistentObjectWithTypedId<TID> entities
        /// </summary>
        /// <param name = "a"></param>
        /// <param name = "b"></param>
        /// <returns>true if CompareTo == 0</returns>
        public static bool operator ==(PersistentObjectWithTypedId<TID> a, PersistentObjectWithTypedId<TID> b)
        {
            if (a.IsNull())
            {
                return b.IsNull();
            }
            return a.CompareTo(b) == 0;
        }

        /// <summary>
        ///     Provides the CompareTo result for two PersistentObjectWithTypedId<TID> entities
        /// </summary>
        /// <param name = "a"></param>
        /// <param name = "b"></param>
        /// <returns>true if CompareTo != 0</returns>
        public static bool operator !=(PersistentObjectWithTypedId<TID> a, PersistentObjectWithTypedId<TID> b)
        {
            return !(a == b);
        }

        /// <summary>
        ///     Provides the CompareTo result for two PersistentObjectWithTypedId<TID> entities
        /// </summary>
        /// <param name = "a"></param>
        /// <param name = "b"></param>
        /// <returns>true if CompareTo < 0</returns>
        public static bool operator <(PersistentObjectWithTypedId<TID> a, PersistentObjectWithTypedId<TID> b)
        {
            if (a.IsNull())
            {
                return false;
            }
            return a.CompareTo(b) < 0;
        }

        /// <summary>
        ///     Provides the CompareTo result for two PersistentObjectWithTypedId<TID> entities
        /// </summary>
        /// <param name = "a"></param>
        /// <param name = "b"></param>
        /// <returns>true if CompareTo > 0</returns>
        public static bool operator >(PersistentObjectWithTypedId<TID> a, PersistentObjectWithTypedId<TID> b)
        {
            if (a.IsNull())
            {
                return false;
            }
            return a.CompareTo(b) > 0;
        }
        #endregion operator

        #region override Object
        /// <summary>
        ///     Compares two entities based on their Business signature 
        ///     (provided by GetDomainObjectSignature())
        /// </summary>
        /// <param name = "obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            var compareTo = obj as PersistentObjectWithTypedId<TID>;

            return (compareTo.Is()) &&
                   (HasSameNonDefaultIdAs(compareTo) ||
                // Since the IDs aren't the same, either of them must be transient to 
                // compare business value signatures
                    (((IsTransient()) || compareTo.IsTransient()) &&
                     HasSameBusinessSignatureAs(compareTo)));
        }

        /// <summary>
        ///     Must be provided to properly compare two objects.
        /// </summary>
        //public override int GetHashCode()
        //{
        //    return (GetType().FullName + "|" +
        //            GetDomainObjectSignature()).GetHashCode();
        //}
        #endregion override Object

        #region IPersistentObject
        /// <summary>
        ///     String representation of the instance.
        ///     Targeted usage is on UI.
        /// </summary>
        /// <returns></returns>
        public abstract string ToDisplay();

        /// <summary>
        ///     This getter returns ToDisplay().
        ///     In some cases is property needed instead of the operation.
        ///     (e.g. ID and Display for ComboBox)
        /// </summary>
        public virtual string Display
        {
            get { return ToDisplay(); }
        }

        /// <summary>
        ///     The overriding method GetDomainObjectSignature should ONLY contain the "business value 
        ///     signature" of the object and not the ID, which is handled by PersistentObjectWithTypedId.  
        ///     The general structure of the overridden method should be as follows:  
        ///     return SomeProperty + "|" + SomeOtherProperty;
        /// </summary>
        protected abstract string GetDomainObjectSignature();
        #endregion IPersistentObject

        #region IPersistentObject Members
        /// <summary>
        ///     Returns the string representing the 'Templated' ID value
        /// </summary>
        public virtual string IDToDisplay
        {
            get { return Id.ToDisplay(formatProvider: CultureInfo.InvariantCulture); }
        }
        #endregion

        #region override abstract
        /// <summary>
        ///     Transient objects are not associated with an item already in storage.  For instance,
        ///     objects to be lazily loaded.
        /// </summary>
        public virtual bool IsTransient()
        {
            return Id.IsNull() || Id.Equals(default(TID));
        }

        /// <summary>
        ///     Calls the GetHashCode to compare this with 'compareTo' instance
        /// </summary>
        /// <param name = "compareTo"></param>
        /// <returns></returns>
        protected internal virtual bool HasSameBusinessSignatureAs(PersistentObjectWithTypedId<TID> compareTo)
        {
            Contract.Requires(compareTo.Is(), "parameter 'compareTo' may not be null");
            return GetHashCode().Equals(compareTo.GetHashCode());
        }

        /// <summary>
        ///     Returns true if self and the provided persistent object have the same ID values 
        ///     and the IDs are not of the default ID value
        /// </summary>
        protected internal virtual bool HasSameNonDefaultIdAs(PersistentObjectWithTypedId<TID> compareTo)
        {
            Contract.Requires(compareTo.Is(), "parameter 'compareTo' may not be null");
            var compareToWithTypedId = compareTo;
            Contract.Assert(compareToWithTypedId.Is(),
                " incorrect parameter supplied to method HasSameNonDefaultIdAs(PersistentObjectWithTypedId compareTo). Expected was PersistentObjectWithTypedId<TID>");

            return (Id.Is() && !Id.Equals(default(TID))) &&
                   (compareToWithTypedId.Id.Is() && !compareToWithTypedId.Id.Equals(default(TID))) &&
                   Id.Equals(compareToWithTypedId.Id);
        }
        #endregion override abstract

        #region IPersistentObjectWithTypedId<TID> Members
        /// <summary>
        ///     ID may be of type int, long, decimal, short, Guid etc.
        ///     Setter is protected to allow set this property via reflection in DAO. 
        ///     Upper layers should only consume this value via public getter.
        /// </summary>
        protected DateTime _createDate;
        protected DateTime _modifyDate;
        protected int _editUser;
        protected bool _isDeleted;

        public virtual TID Id { get; set; }
        public virtual DateTime CreateDate
        {
            get { return _createDate; }
            set { _createDate = value; }
        }

        public virtual DateTime ModifyDate
        {
            get { return _modifyDate; }
            set { _modifyDate = value; }
        }

        public virtual int EditUser
        {
            get { return _editUser; }
            set { _editUser = value; }
        }

        public virtual bool IsDeleted
        {
            get { return _isDeleted; }
            set { _isDeleted = value; }
        }
        #endregion
    }
}